package com.ayu.shortlink.project.util;

import com.ayu.shortlink.project.common.convention.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;

/**
 * 时间戳工具类 (仿雪花算法)
 * 1 bit -> 不使用
 * 41 bit -> 时间戳
 * 22 bit -> 序列号 2^22 = 4194304
 */
@Slf4j
public class TimeStampUtil {
    // 开始时间戳（2021-10-16 22:03:32）
    private final long twepoch = 1634393012000L;

    // 同一时间的序列
    private long sequence;

    // 最近一次的时间戳
    private long lastTimeStamp = -1L;

    // 时间戳偏移量
    private final long timestampLeftShift = 22L;

    // 同一时间戳下的最大序列号
    private final long MAX_SEQUENCE = (1L << 22) - 1;

    // 容忍时间回拨的时间 3s
    private final long patientTime = 3000L;

    // 获取时间戳方法
    private long timeGen(){
        return System.currentTimeMillis();
    }

    // 获取最近一次时间戳
    private long getLastTimeStamp(){
        return lastTimeStamp;
    }

    // 获取下一个随机ID
    public synchronized long nextId() throws ServiceException{
        long timeStamp = timeGen();

        // 发生了时间回拨
        if(timeStamp - patientTime < lastTimeStamp){
            // 日志记录
            log.error("发生时间回拨");
            throw new ServiceException("发生时间回拨");
        }

        // 同一时间戳内
        if (timeStamp == lastTimeStamp){
            sequence = (sequence+1) & MAX_SEQUENCE;
            // 如果为0 则说明同一毫秒的序列号已经用完了
            if (sequence == 0){
                // 那就等待下一个时间戳
                timeStamp = tilNextMillis(timeStamp);
            }
        }else {
            sequence = 0;
        }

        // 记录上一个时间戳
        lastTimeStamp = timeStamp;

        return ((timeStamp-twepoch) << timestampLeftShift) | sequence;
    }

    // 获取下一个时间戳
    private long tilNextMillis(long lastTimeStamp){
        long timeStamp = timeGen();
        while (timeStamp <= lastTimeStamp){
            timeStamp = timeGen();
        }
        return timeStamp;
    }
}